//
// experimental/detail/completion_handler_erasure.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2021-2022 Klemens D. Morgenstern
//                         (klemens dot morgenstern at gmx dot net)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_EXPERIMENTAL_DETAIL_COMPLETION_HANDLER_ERASURE_HPP
#define ASIO_EXPERIMENTAL_DETAIL_COMPLETION_HANDLER_ERASURE_HPP

#include <new>
#include "asio/associated_allocator.hpp"
#include "asio/dispatch.hpp"

namespace asio {

    class any_io_executor;

    namespace experimental {
        namespace detail {

            template<typename Signature, typename Executor>
            struct completion_handler_erasure_base;

            template<typename Func, typename Signature, typename Executor>
            struct completion_handler_erasure_impl;

            template<typename Return, typename... Args, typename Executor>
            struct completion_handler_erasure_base<Return(Args...), Executor> {
                Executor executor;

                completion_handler_erasure_base(Executor &&executor)
                        : executor(std::move(executor)) {
                }

                virtual Return call(Args... args) = 0;

                virtual void destroy() = 0;

                virtual ~completion_handler_erasure_base() = default;
            };

            template<typename Func, typename Return, typename... Args, typename Executor>
            struct completion_handler_erasure_impl<Func, Return(Args...), Executor> final
                    : completion_handler_erasure_base<Return(Args...), Executor> {
                using allocator_base = typename associated_allocator<Func>::type;
                using allocator_type =
                typename std::allocator_traits<allocator_base>::template rebind_alloc<
                        completion_handler_erasure_impl>;

                completion_handler_erasure_impl(Executor &&exec, Func &&func)
                        : completion_handler_erasure_base<Return(Args...), Executor>(
                        std::move(exec)), func(std::move(func)) {
                }

                struct uninit_deleter_t {
                    allocator_type allocator;

                    uninit_deleter_t(const Func &func)
                            : allocator(get_associated_allocator(func)) {
                    }

                    void operator()(completion_handler_erasure_impl *p) {
                        std::allocator_traits<allocator_type>::deallocate(allocator, p, 1);
                    }
                };

                static completion_handler_erasure_impl *make(Executor exec, Func &&func) {
                    uninit_deleter_t deleter(func);
                    std::unique_ptr <completion_handler_erasure_impl, uninit_deleter_t>
                            uninit_ptr(std::allocator_traits<allocator_type>::allocate(
                            deleter.allocator, 1), deleter);
                    completion_handler_erasure_impl *ptr =
                            new(uninit_ptr.get()) completion_handler_erasure_impl(
                                    std::move(exec), std::move(func));
                    uninit_ptr.release();
                    return ptr;
                }

                struct deleter_t {
                    allocator_type allocator;

                    deleter_t(const Func &func)
                            : allocator(get_associated_allocator(func)) {
                    }

                    void operator()(completion_handler_erasure_impl *p) {
                        std::allocator_traits<allocator_type>::destroy(allocator, p);
                        std::allocator_traits<allocator_type>::deallocate(allocator, p, 1);
                    }
                };

                virtual Return call(Args... args) override {
                    std::unique_ptr <completion_handler_erasure_impl,
                    deleter_t> p(this, deleter_t(func));
                    Func f(std::move(func));
                    p.reset();
                    std::move(f)(std::move(args)...);
                }

                virtual void destroy() override {
                    std::unique_ptr<completion_handler_erasure_impl,
                            deleter_t>(this, deleter_t(func));
                }

                Func func;
            };

            template<typename Signature, typename Executor = any_io_executor>
            struct completion_handler_erasure;

            template<typename Return, typename... Args, typename Executor>
            struct completion_handler_erasure<Return(Args...), Executor> {
                struct deleter_t {
                    void operator()(
                            completion_handler_erasure_base<Return(Args...), Executor> *p) {
                        p->destroy();
                    }
                };

                completion_handler_erasure(const completion_handler_erasure &) = delete;

                completion_handler_erasure(completion_handler_erasure &&) = default;

                completion_handler_erasure &operator=(
                        const completion_handler_erasure &) = delete;

                completion_handler_erasure &operator=(
                        completion_handler_erasure &&) = default;

                constexpr completion_handler_erasure() = default;

                constexpr completion_handler_erasure(nullptr_t)
                        : completion_handler_erasure() {
                }

                template<typename Func>
                completion_handler_erasure(Executor exec, Func &&func)
                        : impl_(completion_handler_erasure_impl<
                        std::decay_t < Func>, Return(Args...), Executor > ::make(
                        std::move(exec), std::forward<Func>(func))) {
                }

                ~completion_handler_erasure() {
                    if (impl_) {
                        Executor executor(impl_->executor);
                        asio::dispatch(executor,
                                       [impl = std::move(impl_)]() mutable {
                                           impl.reset();
                                       });
                    }
                }

                Return operator()(Args... args) {
                    if (impl_)
                        impl_.release()->call(std::move(args)...);
                }

                constexpr bool operator==(nullptr_t) const

                noexcept { return impl_ == nullptr; }

                constexpr bool operator!=(nullptr_t) const

                noexcept { return impl_ != nullptr; }

                constexpr bool operator!() const

                noexcept { return impl_ == nullptr; }

            private:
                std::unique_ptr <
                completion_handler_erasure_base<Return(Args...), Executor>, deleter_t>
                        impl_;
            };

        } // namespace detail
    } // namespace experimental
} // namespace asio

#endif // ASIO_EXPERIMENTAL_DETAIL_COMPLETION_HANDLER_ERASURE_HPP
